/****************************************************************
 * <strong>MFMVCManager</strong>
 *
 * <p>
 * MFMVCManager
 * </p>
 *
 * @author AndyZhang 2015
 * @version 0.1
 * ToDo:2015-3-3
 ***************************************************************/

package com.thinkwide.data.mvc.config;

import com.thinkwide.data.ormapping.MFDbTypeTransfer;
import com.thinkwide.data.ormapping.MFReflect;
import com.thinkwide.data.core.MFPath;
import com.thinkwide.data.core.MFString;
import com.thinkwide.data.mvc.core.MFBean;
import com.thinkwide.data.mvc.core.MFMVCReturnType;
import com.thinkwide.data.mvc.view.MFJsonView;
import com.thinkwide.data.mvc.view.MFJstlView;
import com.thinkwide.data.mvc.view.MFModelAndView;
import com.thinkwide.data.mvc.view.MFRedirectView;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class MFMVCManager {

    private static List<MFBean> interceptorList = new ArrayList<MFBean>();
    private static List<MFBean> beanList = new ArrayList<MFBean>();
    private static List<String> scanList = new ArrayList<String>();
    private MFMappingItems mappingitems;

    private boolean mvc_debug;

    public MFMVCManager() {
        mappingitems = new MFMappingItems();
    }

    public Map<String, String> getParameters(HttpServletRequest request) {
        Enumeration params = request.getParameterNames();
        while (params.hasMoreElements()) {
            String name = (String) params.nextElement();
            String value = request.getParameter(name);
        }

        Map<String, String> map = new HashMap<String, String>();
        return map;
    }

    public boolean prepareProperty(Class<?> classType, Object obj, HttpServletRequest request) throws Throwable {
        Enumeration params = request.getParameterNames();
        while (params.hasMoreElements()) {
            String name = (String) params.nextElement();
            String value = request.getParameter(name);

            if (MFString.notNullOrEmpty(name) && MFReflect.isHaveProperty(obj, name)) {
                Class<?> type = MFReflect.getPropertyType(obj, name);
                Object typedValue = MFDbTypeTransfer.toJavaType(type, value);
                MFReflect.setProperty(obj, name, typedValue);

            }
        }
        return true;
    }

    public Object getRequestParam(String paramName, Class<?> type, HttpServletRequest request) throws Throwable {
        Enumeration params = request.getParameterNames();
        while (params.hasMoreElements()) {
            String name = (String) params.nextElement();
            String value = request.getParameter(name);

            if (MFString.notNullOrEmpty(name)) {
                Object typedValue = MFDbTypeTransfer.toJavaType(type, value);
                return typedValue;

            }
        }
        return null;
    }

    public boolean runMapping(MFMappingItem item, HttpServletRequest request, HttpServletResponse response) throws Throwable {
        if (item != null) {
            Class<?> c = Class.forName(item.getClassName());

            Object obj = c.newInstance();

            String methodName = item.getMethodName();
            //Method method=c.getDeclaredMethod(methodName, null);
            //Class[] parameterTypes = method.getParameterTypes();

            List<Class> paramClass = new ArrayList<Class>();
            List<Object> paramsValue = new ArrayList<Object>();
            for (MFXParam param : item.getParams()) {
                //MFDebug.println(param.getParamType());

                String paramName = MFString.isNullOrEmpty(param.getValue()) ? param.getParamName() : param.getValue();


                if (MFString.compareNoCase(param.getParamType(), Integer.class.getName())) {
                    Object instance = getRequestParam(paramName, Integer.class, request);
                    paramClass.add(Integer.class);
                    paramsValue.add(instance);
                } else if (MFString.compareNoCase(param.getParamType(), Float.class.getName())) {
                    Object instance = getRequestParam(paramName, Float.class, request);
                    paramClass.add(Float.class);
                    paramsValue.add(instance);
                } else if (MFString.compareNoCase(param.getParamType(), Double.class.getName())) {
                    Object instance = getRequestParam(paramName, Double.class, request);
                    paramClass.add(Double.class);
                    paramsValue.add(instance);
                } else if (MFString.compareNoCase(param.getParamType(), HttpServletRequest.class.getName())) {
                    paramClass.add(HttpServletRequest.class);
                    paramsValue.add(request);
                } else if (MFString.compareNoCase(param.getParamType(), HttpServletResponse.class.getName())) {
                    paramClass.add(HttpServletResponse.class);
                    paramsValue.add(response);

                } else {

                    Class<?> clazz = Class.forName(param.getParamType());
                    paramClass.add(clazz);
                    Object instance = clazz.newInstance();

                    if (instance instanceof String ||
                            instance instanceof Integer ||
                            instance instanceof Float) {
                        instance = getRequestParam(paramName, clazz, request);
                    } else {
                        prepareProperty(clazz, instance, request);
                        //MFReflect.SetProperty(instance, "name", "123");
                    }

                    paramsValue.add(instance);
                }
            }

            Class[] array = new Class[paramClass.size()];
            Method method = c.getDeclaredMethod(methodName, (Class[]) paramClass.toArray(array));
            method.setAccessible(true);

            Object value = method.invoke(obj, paramsValue.toArray());

            if (value != null) {
                if (item.getReturnType() == MFMVCReturnType.View) {
                    if (value instanceof String) {
                        String url = value.toString();
                        if (url.toLowerCase().startsWith("redirect:")) {
                            url = url.substring(10);
                            MFRedirectView rv = new MFRedirectView(url);
                            rv.render(null, request, response);
                        } else {
                            MFJstlView jstl = new MFJstlView(url);
                            jstl.render(null, request, response);

                        }
                        //response.sendRedirect((String)value);
                    } else if (value instanceof MFModelAndView) {
                        MFModelAndView mv = ((MFModelAndView) value);
                        if (mv.getView() != null) {
                            mv.getView().render(mv.getModel(), request, response);
                        } else {
                            MFJstlView jstl = new MFJstlView(mv.getViewName());
                            jstl.render(mv.getModel(), request, response);
                        }

                    }
                }


                if (item.getReturnType() == MFMVCReturnType.Json) {
                    if (value instanceof Map) {
                        MFJsonView view = new MFJsonView((Map) value);
                        view.render(null, request, response);
                    } else if (value instanceof List) {
                        MFJsonView view = new MFJsonView((List) value);
                        view.render(null, request, response);
                    } else if (value instanceof Object) {
                        MFJsonView view = new MFJsonView((Object) value);
                        view.render(null, request, response);
                    }
                }


                if (item.getReturnType() == MFMVCReturnType.Text) {
                    response.getWriter().println(value);
                }

            }

        }
        return false;
    }

    public boolean runMapping(String url, HttpServletRequest request, HttpServletResponse response) throws Throwable {
        MFMappingItem item = findMapping(url);
        return runMapping(item, request, response);
    }

    public String CheckPath(String url) {
        if (MFString.notNullOrEmpty(url) && !url.startsWith(MFPath.useparator)) {
            url = MFPath.useparator + url;
        }
        return url;
    }

    public MFMappingItem findMapping(String url) {
        for (MFMappingItem item : mappingitems) {
            String Str = item.getUrl();
            Str = CheckPath(Str);

            if (MFString.compareNoCase(url, Str)) {
                //MFDebug.println("find");
                return item;
            }
        }
        return null;
    }

    public String getAttrValue(Element item, String name) {
        Attribute attr = item.attribute(name);
        if (attr != null) {
            String text = attr.getText();
            return text;
        }
        return null;
    }

    public void scanAnnotation() throws Exception {
        for (String item : scanList) {
            MFMVCReflect.scanClassList(mappingitems, item);
        }

    }

    public boolean loadConfig(String filePath) throws DocumentException {
        File file = new File(filePath);

        SAXReader reader = new SAXReader();
        Document doc = reader.read(file);
        Element root = doc.getRootElement();

        Element config = root.element("annotation-config");
        Element driven = root.element("annotation-driven");
        Element handler = root.element("default-servlet-handler");

        Element debug = root.element("annotation-debug");
        mvc_debug = debug != null;
        //load scan

        for (Iterator<?> i = root.elementIterator("scan"); i.hasNext(); ) {
            Element item = (Element) i.next();
            String pk = getAttrValue(item, "package");
            scanList.add(pk);
        }

        //load bean
        for (Iterator<?> i = root.elementIterator("bean"); i.hasNext(); ) {
            Element item = (Element) i.next();
            MFBean bean = new MFBean();
            String name = getAttrValue(item, "name");
            String className = getAttrValue(item, "class");

            bean.setName(name);
            bean.setClassName(className);

            beanList.add(bean);
        }

        //load mvc:interceptors
        for (Iterator<?> i = root.elementIterator("interceptors"); i.hasNext(); ) {
            Element interceptorsItem = (Element) i.next();

            for (Iterator<?> j = interceptorsItem.elementIterator("bean"); j.hasNext(); ) {
                Element beanItem = (Element) j.next();

                String actionName = getAttrValue(beanItem, "name");
                String className = getAttrValue(beanItem, "class");
                MFBean bean = new MFBean();

                interceptorList.add(bean);
            }
        }

        return true;
    }

    public void setMappingitems(MFMappingItems mappingitems) {
        this.mappingitems = mappingitems;
    }

    public MFMappingItems getMappingitems() {
        return mappingitems;
    }

    public void setMvc_debug(boolean mvc_debug) {
        this.mvc_debug = mvc_debug;
    }

    public boolean isMvc_debug() {
        return mvc_debug;
    }

}
